{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a3ac9671-fedd-4883-baa7-299ff0d06590",
   "metadata": {},
   "source": [
    "接收的不是 JSON，而是表单字段时，要使用 Form。\n",
    "\n",
    "要使用表单，需预先安装 python-multipart。\r\n",
    "\r\n",
    "例如，pip install python-multipart。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39bf585a-20db-48f9-8915-8cc8bbea9152",
   "metadata": {},
   "source": [
    "Form 是直接继承自 Body 的类。\n",
    "\n",
    "声明表单体要显式使用 Form ，否则，FastAPI 会把该参数当作查询参数或请求体（JSON）参数。\n",
    "\n",
    "创建表单（Form）参数的方式与 Body 和 Query 一样：\n",
    "\n",
    "例如，OAuth2 规范的 \"密码流\" 模式规定要通过表单字段发送 username 和 password。\r\n",
    "\r\n",
    "该规范要求字段必须命名为 username 和 password，并通过表单字段发送，不能用 JSON。\r\n",
    "\r\n",
    "使用 Form 可以声明与 Body （及 Query、Path、Cookie）相同的元数据\n",
    "\n",
    "与 JSON 不同，HTML 表单（<form></form>）向服务器发送数据通常使用「特殊」的编码。\r\n",
    "\r\n",
    "FastAPI 要确保从正确的位置读取数据，而不是读取 JSON。表单数据的「媒体类型」编码一般为 application/x-www-form-urlencoded。\r\n",
    "\r\n",
    "但包含文件的表单编码为 multipart/form-data。\n",
    "\n",
    "和验证。\n",
    "\n",
    "从 fastapi 导入 Form："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "80e460b9-b794-4dab-a3d8-46866da01660",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [23400]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:63035 - \"POST /login/ HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:63041 - \"POST /login/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [23400]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Form\n",
    "app = FastAPI()\n",
    "@app.post(\"/login/\")\n",
    "async def login(username: str = Form(), password: str = Form()):\n",
    "    return {\"username\": username}\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9644bf59-997d-425a-a21b-6f486d910a4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'http://127.0.0.1:8009/login/' \n",
    "headers = {\"content-type\": \"application/x-www-form-urlencoded\"} \n",
    "data = {\"username\": \"mockuser\", \"password\": \"123456\"}\n",
    "res = requests.post(url, data=data, headers=headers) \n",
    "res.text\n",
    "\n",
    "# 在另一个ipynb文件中运行代码，会得到 '{\"username\":\"mockuser\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "876b1b43-6c22-47ea-a661-0e9bc467425d",
   "metadata": {},
   "source": [
    "可在一个路径操作中声明多个 Form 参数，但不能同时声明要接收 JSON 的 Body 字段。\n",
    "\n",
    "因为此时请求体的编码是 application/x-www-form-urlencoded，不是 application/json。\r\n",
    "\r\n",
    "这不是 FastAPI 的问题，而是 HTTP 协议的规定。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
